`timescale 1ns/1ps
`default_nettype none

module tb_rv32ui;

  // ========= 时钟/复位 =========
  reg clk  = 1'b0;
  reg rstn = 1'b0;
  always #5 clk = ~clk;  // 100 MHz

  // ========= 监控 DMEM 端口 =========
  wire [31:0] mon_addr  ;
  wire [31:0] mon_wdata;
  wire [3:0]  mon_we    ;

  // 可选：GPIO 对外引脚（不使用也可悬空）
  wire [7:0]  gpio_out;
  // ========= DUT =========
  soc_top dut(
    .clk      (clk),
    .rstn     (rstn),
    .mon_addr (mon_addr),
    .mon_wdata(mon_wdata),
    .mon_we   (mon_we),
    .gpio_out (gpio_out)
  );

  // ========= 运行参数（plusargs）=========
  // +imem=<.../imem_words.hex>       // 指令镜像（按“字”）
  // +imem_bytes=<.../imem.hex>       // （新增）把字节版镜像也加载到 DMEM，供数据口读取 LMA
  // +test=<name>                     // 仅用于打印
  // +max_cycles=<N>                  // 看门狗
  // +tohost=<addr>                   // 缺省 0x4000
  reg [1023:0] imem_file  = "imem_words.hex";
  reg [1023:0] imem_bytes = "imem.hex";                 // 为空则不做镜像；推荐传入
  reg [1023:0] testname   = "rv32ui-unknown";
  integer      max_cycles = 2_000_000;
  reg [31:0]   TOHOST_ADDR = 32'h0000_4000;     // 与 link.ld 钉死地址一致

  integer arg_hex, arg_dec;

  // ========= 初始化 =========
  integer i;
  reg [1023:0] vcdfile;
  initial begin
    if ($value$plusargs("vcd=%s", vcdfile)) begin
      $display("[VCD] dump to: %0s", vcdfile);
      $dumpfile(vcdfile);
    end else begin
      $dumpfile("src/build/rv32ui.vcd");  
    end
    $dumpvars(0, tb_rv32ui);

    if ($value$plusargs("imem=%s",       imem_file))  ;
    if ($value$plusargs("imem_bytes=%s", imem_bytes)) ;
    if ($value$plusargs("test=%s",       testname))   ;
    if ($value$plusargs("max_cycles=%d", max_cycles)) ;

    if ($value$plusargs("tohost=%h", arg_hex)) begin
      TOHOST_ADDR = arg_hex[31:0];
    end else if ($value$plusargs("tohost=%d", arg_dec)) begin
      TOHOST_ADDR = arg_dec[31:0];
    end

    //调试，用来显示TB信息
    $display("TB: +imem       = %0s", imem_file);
    if (imem_bytes != "") $display("TB: +imem_bytes = %0s", imem_bytes);
    else                  $display("TB: +imem_bytes = <none> (建议传入字节版镜像以供 LMA 搬运)");
    $display("TB: +test       = %0s", testname);
    $display("TB: +max_cycles = %0d", max_cycles);
    $display("TB: tohost      = 0x%08x", TOHOST_ADDR);

    // 1) 加载 IMEM（按“字”的指令数组；支持 @word 索引）
    $display("TB: loading IMEM (words) from %0s", imem_file);
    $readmemh(imem_file, dut.u_cpu.u_imem.mem);

    // 2) 把“字节版”程序镜像也写入 DMEM（供数据口读取 LMA）
    //    这样 crt0.S 才能从 <0x3000 的 LMA 正确搬运 .data / 清零 .bss
    if (imem_bytes != "") begin
      $display("TB: mirroring IMEM bytes into DMEM from %0s", imem_bytes);
      $readmemh(imem_bytes, dut.u_dmem.u_dmem.mem);
    end

    // 简短确认程序加载情况
    #1;
    $display("IMEM[0..7] words:");
    for (i=0; i<8; i=i+1)
      $display("  [%0d]=0x%08x", i, dut.u_cpu.u_imem.mem[i]);

    // 复位
    rstn = 1'b0;
    repeat (5) @(posedge clk);
    rstn = 1'b1;
  end

  // ========= 心跳 + 超时 =========
  integer cyc = 0;
  always @(posedge clk) if (rstn) begin
    cyc <= cyc + 1;
    if (cyc % 10000 == 0)
      $display("[HB %0d] IF.pc=0x%08x", cyc, dut.u_cpu.if_pc);
    if (cyc >= max_cycles) begin
      $display("[TIMEOUT] %0s over %0d cycles; PC=0x%08x", testname, max_cycles, dut.u_cpu.if_pc);
      $display("  gp(x3)=0x%08x  a0(x10)=0x%08x", dut.u_cpu.u_rf.regs[3], dut.u_cpu.u_rf.regs[10]);
      #1 $finish;
    end
  end

  // ========= DMEM 写监控（便于定位MEM写问题）=========
  integer wr_seen = 0;
  always @(posedge clk) if (rstn) begin
    if (mon_we != 4'b0000) begin
      if (wr_seen < 64) begin
        $display("[DMEM-W %0d] addr=0x%08x data=0x%08x we=%b", wr_seen, mon_addr, mon_wdata, mon_we);
        wr_seen = wr_seen + 1;
      end
    end
  end

  // ========== I/D 一致性小补丁：把 DMEM 的写同步到 IMEM ==========
  // DMEM->IMEM 镜像（自改代码补丁）
  localparam integer IMEM_BYTES_TB = 256*1024; // 依你 imem 尺寸
  reg [31:0] base, w_old, wdata_al, mask;
  always @(posedge clk) if (rstn && mon_we!=4'b0000) begin
    
    base = {mon_addr[31:2], 2'b00};

    if (base < IMEM_BYTES_TB) begin
      w_old    = dut.u_cpu.u_imem.mem[base[31:2]];
      // 1) 数据按地址对齐（与 dmem 写入规则一致）
      wdata_al = mon_wdata ;

      // 2) 生成字节掩码（**不要再移位**）
      mask = 32'h0;
      if (mon_we[0]) mask |= 32'h000000FF;
      if (mon_we[1]) mask |= 32'h0000FF00;
      if (mon_we[2]) mask |= 32'h00FF0000;
      if (mon_we[3]) mask |= 32'hFF000000;

      // 3) 按字节打补丁
      dut.u_cpu.u_imem.mem[base[31:2]] = (w_old & ~mask) | (wdata_al & mask);

      // 调试打印（可留）
      // $display("[COH] IMEM[%0d] <= old=0x%08x mask=0x%08x new=0x%08x",
      //         base[31:2], w_old, mask, (w_old & ~mask) | (wdata_al & mask));
    end
  end


  // ========= 以 tohost 为唯一判定 =========
  // 组合 64-bit 帧：低/高各一次 SW；若仅低写入 1 也视为 PASS（兼容最简协议）
  reg [31:0] tohost_lo, tohost_hi;
  reg        lo_valid,  hi_valid;
  reg [63:0] tohost64;

  always @(posedge clk) if (rstn && mon_we!=4'b0000) begin
    if (mon_addr == TOHOST_ADDR) begin
      tohost_lo <= mon_wdata;
      lo_valid  <= 1'b1;

      // 兼容“只写低 32 位且值为 1”的简化用法
      if (mon_wdata == 32'd1) begin
        // $display("[TOHOST] low<=1 (single-word) -> PASS");
        $display("[PASS]32bit %0s (TOHOST=1).", testname);
        #1 $finish;
      end
    end
    if (mon_addr == (TOHOST_ADDR + 32'd4)) begin
      tohost_hi <= mon_wdata;
      hi_valid  <= 1'b1;
    end
  end

  // 两半都到齐再判定（标准 64-bit tohost 帧） 64位CPU才使用
  always @(posedge clk) if (rstn) begin
    if (lo_valid && hi_valid) begin
      lo_valid <= 1'b0; hi_valid <= 1'b0;
      tohost64 = {tohost_hi, tohost_lo};
      // $display("[TOHOST] 0x%08x/0x%08x => 0x%016x", TOHOST_ADDR, TOHOST_ADDR+4, tohost64);

      if (tohost64 == 64'd1) begin
        $display("[PASS]64bit %0s (TOHOST=1).", testname);
        #1 $finish;
      end else begin
        $display("[FAIL] %0s (TOHOST=%0d / 0x%016x). gp=0x%08x a0=0x%08x",
                 testname, tohost64, tohost64, dut.u_cpu.u_rf.regs[3], dut.u_cpu.u_rf.regs[10]);
        #1 $finish;
      end
    end
  end

  // ========= 基础一致性：x0 恒 0 =========
  always @(posedge clk) if (rstn) begin
    if (dut.u_cpu.u_rf.regs[0] !== 32'd0) begin
      $display("x0 must be 0, got=0x%08x", dut.u_cpu.u_rf.regs[0]);
      #1 $finish;
    end
  end

endmodule

`default_nettype wire
